/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.application;

import com.jb.faces.el.AttributeEvaluator;
import com.jb.util.ClassUtil;
import com.jb.web.bean.event.UIEvent;
import com.jb.web.bean.event.UIEventHandler;
import com.jb.web.context.WebContext;

import java.io.File;

import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * Event manager centralizes the event handling for all the requests. It is implemented as a
 * singleton. To process an event, call EventManager.handleEvent, which will create a new event
 * and hand it to the event handler passed.
 *
 * @author twang
 * @version 1.0
 */
public class EventManager {
    static private EventManager eventManager = new EventManager();
    static private Log log = LogFactory.getLog(EventManager.class.getName());

    public static EventManager getInstance() {
        return eventManager;
    }
    
    public static void processEvent(WebContext ctx, Object source, String eventHandler) {
        eventManager.handleEvent(ctx, source, eventHandler);
    }

    public void handleEvent(WebContext ctx, Object source, String eventHandler, UIEvent event) {
        if (log.isDebugEnabled()) {
            log.debug("Handle event " + eventHandler + " for " + source);
        }

        try {
            handleEvent(ctx, event, eventHandler);
        } catch (Throwable e) {
            if (log.isErrorEnabled()) {
                log.error(source.getClass().getName() + ":" + eventHandler, e);
            }
            ctx.addErrorMessage(source, "Failed to handle event with handler " + eventHandler);
        }
    }

    public void handleEvent(WebContext ctx, Object source, String eventHandler) {
        if (log.isDebugEnabled()) {
            log.debug("Handle event " + eventHandler + " for " + source);
        }

        try {
            if(eventHandler.startsWith("client:")) {
                if (log.isDebugEnabled()) 
                    log.debug("Client Event, do nothing");
                return;
            }

            if (eventHandler.startsWith("${")) {
                Object eventSource= source;
                if (source instanceof UIEvent) {
                    eventSource = ((UIEvent)source).getSource();
                }
                processELEvent(ctx, eventSource, eventHandler);
            } else if (source instanceof UIEvent) {
                handleEvent(ctx, (UIEvent) source, eventHandler);
            } else {
                UIEvent uiEvent = new UIEvent(source, ctx.getRequest());
                handleEvent(ctx, uiEvent, eventHandler);
            }
        } catch (Throwable e) {
            if (log.isErrorEnabled()) {
                log.error(source.getClass().getName() + ":" + eventHandler, e);
            }

            ctx.addErrorMessage(source, "Failed to handle event with handler " + eventHandler);
        }
    }

    public String processELEvent(WebContext ctx, Object comp, String expression) {
        try {
            return (String)AttributeEvaluator.evaluate(ctx, comp, expression, String.class);
        } catch (Throwable e) {
            ctx.addErrorMessage(comp, e.toString() + " for " + comp);

            return expression;
        }
    }

    private boolean handleEvent(WebContext ctx, UIEvent uiEvent, String eventHandler) {
        String rootLocation = ctx.getApplicationTop() + File.separator;
        String methodName = eventHandler.substring(eventHandler.lastIndexOf(".") + 1);

        //if last entity start with on
        //it is a method to call
    
        if (methodName.startsWith("on")) {
            //
            String className = eventHandler.substring(0, eventHandler.lastIndexOf("."));

            //if class name starts with el, it is assumed className (without el: is a el object)
            //el:application.onAddVariable
            //com.jb.web.event.onClick
            if (className.startsWith("el:")) {
                try {
                    className = className.substring(3);

                    Object obj = AttributeEvaluator.evaluate(ctx, uiEvent.getSource(), "${" + className + "}");
                    Method method = obj.getClass().getMethod(methodName, new Class[] { WebContext.class, UIEvent.class });
                    method.invoke(obj, new Object[] { ctx, uiEvent });
                } catch (Exception e) {
                    ctx.addErrorMessage(this, "Failed to execute event handler " + eventHandler);
                    ctx.renderResponse();
                }
            } else {
                try {
                    Class clazz = ClassUtil.getClass(className);
                    Method method = clazz.getMethod(methodName, new Class[] { WebContext.class, UIEvent.class });
                    method.invoke(clazz.newInstance(), new Object[] { ctx, uiEvent });
                } catch (Exception e) {
                    ctx.addErrorMessage(this, "Failed to execute event handler " + eventHandler);
                    ctx.renderResponse();
                }
            }
        } else {
            UIEventHandler eh = (UIEventHandler)ClassUtil.createInstance(eventHandler, rootLocation);

            if (eh != null) {
                try {
                    eh.handleEvent(ctx, uiEvent);
                } catch (Throwable t) {
                    ctx.addErrorMessage(uiEvent.getSource(), "Failed to execute " + eventHandler + " due to " + t);
                }
            } else {
                ctx.addErrorMessage(uiEvent.getSource(), "Failed to create event handler " + eventHandler);
            }
        }

        return false;
    }
}
